package homework.server;

import homework.server.terminal.Command;
import homework.server.terminal.impl.CalcCommand;
import homework.server.terminal.impl.GetVariableCommand;
import homework.server.terminal.impl.QuitCommand;
import homework.server.terminal.impl.SetVariableCommand;
import homework.server.worker.TerminalWorker;
import homework.server.worker.WorkerFactory;

import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class TerminalServer implements Closeable {

    private ServerSocket socket;
    private HashMap<String, String> globalEnv = new HashMap<String, String>();
    private HashMap<String, Command> cmds = new HashMap<String, Command>();
    private WorkerFactory factory;
    private ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 8, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>());

    private Thread serverDaemon = null;

    public TerminalServer(int port, WorkerFactory factory) throws IOException {
        socket = new ServerSocket(port);
        this.factory = factory;
    }

    public TerminalServer(int port, int backlog, WorkerFactory factory) throws IOException {
        socket = new ServerSocket(port, backlog);
        this.factory = factory;
    }

    public TerminalServer(int port, int backlog, InetAddress addr, WorkerFactory factory) throws IOException {
        socket = new ServerSocket(port, backlog, addr);
        this.factory = factory;
    }

    public void start(boolean isDaemon) {
        Runnable runnable = () -> {
            while (true) {
                Socket client;
                OutputStream out;
                try {
                    client = socket.accept();
                } catch (IOException e) {
                    e.printStackTrace();
                    continue;
                }

                Runnable worker;
                try {
                    worker = factory.createWorker(client, globalEnv, cmds);
                } catch (IOException e) {
                    e.printStackTrace();
                    continue;
                }

                pool.execute(worker);
            }
        };

        serverDaemon = new Thread(runnable);
        serverDaemon.start();
        if(!isDaemon) {
            try {
                serverDaemon.join();
            } catch (InterruptedException ignored) {

            }
        }
    }

    @Override
    public void close() throws IOException {
        if(serverDaemon.isAlive()) {
            // XXX: 温和的关闭
            serverDaemon.stop();
            socket.close();
            pool.shutdownNow();
        }
    }

    public Command setCommand(String name, Command cmd) {
        return cmds.put(name, cmd);
    }

    public String setGlobalVariable(String name, String value) {
        if(value == null) {
            globalEnv.remove(name);
            return null;
        }
        return globalEnv.put(name, value);
    }

    public static void main(String[] args) throws IOException {
        TerminalServer server = new TerminalServer(23333, TerminalWorker::new);

        Command quitCmd = new QuitCommand("Bye");
        server.setCommand("quit", quitCmd);
        server.setCommand("exit", quitCmd);
        server.setCommand("close", quitCmd);

        server.setCommand("set", new SetVariableCommand());
        server.setCommand("get", new GetVariableCommand());
        server.setCommand("calc", new CalcCommand());

        server.setGlobalVariable("LANG", "utf-8");
        server.setGlobalVariable("ERROR_FORMAT", "[ERROR] %s:%s");

        server.start(true);

//        server.close();
    }
}
